/*
 * cm_log.h
 *
 *  Created on: 2015年10月26日
 *      Author: chens
 */

#ifndef LOG_CM_LOG_H_
#define LOG_CM_LOG_H_

#define LOG_WRITE_CODE
#define LOG_QUERY_CODE
#define LOG_CLIENT_CODE
#define LOG_SERVER_CODE

#include <stdio.h> // 宏cm_log中使用了snprintf
#include <string.h> // 宏cm_log中使用了memset
#include <time.h>

#ifdef __cplusplus
extern "C" {
#endif

/**************************************************
 * 一、公共定义部分
 */

//@warning MAX_LOG_COUNT 应当至少大于 MAX_CACHE
#define MAX_LOG_LEN 1024 // 每条记录最大长度(文本)
#define MAX_LOG_COUNT  10000 // 最大记录条数
#define MAX_CACHE   256 //  定义缓冲区的个数
#define MAX_LOG_CMD_LEN 256 // 协议中，用来缓存命令的buffer大小

#define DEFAULT_LOG_FILE    "./log.log"

enum log_level {
    LOG_DEBUG, /* 细粒度信息事件，对调试应用程序有帮助的信息 */
    LOG_INFO, /* 在粗粒度级别上突出强调应用程序的运行过程 */
    LOG_WARN, /* 会出现潜在错误的情形 */
    LOG_ERROR, /* 虽然发生错误事件，但仍然不影响系统的继续运行 */
    LOG_FATAL /* 严重的错误事件, 将会导致应用程序的退出 */
};

#define DEFAULT_LOG_LEVEL   LOG_DEBUG    // 仅大于等于此级别的日志才进行记录
#define DEFAULT_PRINT_LEVEL LOG_INFO    // 仅大于等于此级别的日志才打印输出

#define LOG_VERSION "LOG 0.0"

struct log_head {
    char version[16];   // 版本信息
    char length[16];    // 除头部外，剩余的数据大小
    char command[32];   // 命令
    char content_start; // 占位符，内容从这里开始
};

#define LOG_HEAD_SIZE   64 // 固定64字节头部长度

/*
 * @brief   初始化日志模块，指定数据文件路径，启动缓存管理线程
 * @param   fpath: 日志文件(db)全路径
 * @warning 写日志前必须先调用一次
 */
void log_setup(const char *fpath);

/*
 * @brief   设置日志输出模式
 * @param   print_level: 大于等于这个级别的日志则输出到stdout
 *          write_db_level: 大于等于这个级别的日志则写入数据库
 *          不调用则使用 DEFAULT_LOG_LEVEL 和 DEFAULT_PRINT_LEVEL
 */
void log_set_output_level(enum log_level print_level,
        enum log_level write_db_level);

/*
 * @brief   设置最大记录条数，不调用默认使用 MAX_LOG_COUNT 宏
 *          count应当大于（远大于）MAX_CACHE定义的值
 */
void log_set_max_count(unsigned int count);

void log_destroy();

/**************************************************
 * 二、写日志部分
 */
#ifdef LOG_WRITE_CODE
/*
 * @brief   刷新日志模块的缓存，全部写入数据库
 * @warning 写数据库可能比较耗时(100ms级)，频繁调用可能降低程序性能
 */
void log_flush();

/*
 * @brief   记录日志接口, 内部有缓存，不一定马上写入数据库文件
 *          可以通过调用 log_flush 立刻写入数据库
 * @param   level详见enum log_level的定义
 * @param   content: 日志内容，字符串长度不应超过 MAX_LOG_LEN, 超过部分不会记录
 */
void log_impl(enum log_level level, const char * content);

/*
 * @brief   可接受可变参数的记录日志接口, 实际调用 log_impl
 */
#define cm_log(level, format, args...) \
    do { \
        char log_buf[MAX_LOG_LEN];\
        snprintf(log_buf, MAX_LOG_LEN, format, ##args);\
        log_impl(level, log_buf);\
    } while(0)

/*
 * @brief   与printf格式一致的各个日志方法
 */
#define log_debug(format, args...)    cm_log(LOG_DEBUG, format, ##args)
#define log_info(format, args...)    cm_log(LOG_INFO, format, ##args)
#define log_warn(format, args...)    cm_log(LOG_WARN, format, ##args)
#define log_error(format, args...)    cm_log(LOG_ERROR, format, ##args)
#define log_fatal(format, args...)    cm_log(LOG_FATAL, format, ##args)

#endif // LOG_WRITE_CODE

/**************************************************
 * 三、查询
 */
#ifdef LOG_QUERY_CODE
/*
 * PARAM:
 *  path: 指定日志文件路径及文件名
 *  time_t类型（1970起秒数）的begin end参数指定所要查询日志的时间返回
 *  max参数指定当次查询最多只需要查询多少条记录
 *  buf是出参，用于返回当次查询内部分配的用于存放日志的缓存区
 *
 *  log_query_level 接口可以查询指定级别的日志
 *  log_query_levels 接口可以查询两个级别之间的日志，注意 lv_begin 必须小于等于 lv_end, 否则将返回0且buf为NULL
 *
 * RETURN:
 *  查询接口的返回值均为所返回的 buf 中存放数据的长度, 负数表示出错，当返回 <= 0 时，出参buf将返回NULL
 *
 * WARNING:
 *  出参buf所返回的缓存在使用完后必须调用 log_freebuf进行释放，否则将造成内存泄漏
 */

int log_query_time(time_t begin, time_t end, int max, char **buf);

int log_query_level(enum log_level lv, time_t begin, time_t end, int max,
        char **buf);

int log_query(enum log_level lv_begin, enum log_level lv_end, time_t begin,
        time_t end, int max, char **buf);

enum log_order {
    LOG_TIME_DESC, LOG_TIME_ASC, LOG_ID_DESC, LOG_ID_ASC
};

int log_query_order(enum log_level lv_begin, enum log_level lv_end,
        time_t begin, time_t end, int max, char **buf, enum log_order order);

/*
 * @brief   查询当前状态
 * @return  在三个出参中分别返回
 *              count: 当前一共有多少条记录，
 *              begin: 最早一条记录的时间
 *              end: 最晚一条记录的时间
 */
int log_query_stat(int *count, time_t *begin, time_t *end);

/*
 * @brief   释放查询接口所返回的buffer
 */
void log_freebuf(char *buf);

#endif // LOG_QUERY_CODE

/**************************************************
 * 四、日志客户端
 */

#ifdef LOG_CLIENT_CODE

#define CREATE_LOG_CLIENT_TIMEOUT   5   // 控制create_log_client的超时时间
#define LOG_PORT    9528    // 控制日志服务器所监听的TCP端口

/*
 * @brief   创建日志客户端，具有指定的超时时间
 * @param   server: 指定开放了日志端口的服务器IP地址
 * @return  创建成功则返回有效的socket描述符 >= 0
 *          创建过程中出错，或者connect在指定超时范围内没有成功，则返回-1
 */
int log_client_create(const char * server);

/*
 * @brief   释放查询函数中返回的指针
 */
void log_client_freebuf(char *buf);

/*
 * @brief   向服务器发送查询命令，并等待返回结果，查询结果保存在buf指向的指针处
 *          buf在函数内部分配，使用完后必须调用 log_client_freebuf 进行释放
 * @return  -2: 服务器端错误，错误信息在buf中
 *          -1: 表示fd错误，应当关闭fd
 *          0 : 没有查询结果
 *          >0: 表示buf中存放的数据大小
 */
int log_client_query(int fd, enum log_level lv_begin, enum log_level lv_end,
        time_t begin, time_t end, int max, char **buf);

/*
 * @brief   向服务器发送查询当前数据库状态的命令
 * @return  在三个出参中分别返回
 *              count: 当前一共有多少条记录，
 *              begin: 最早一条记录的时间
 *              end: 最晚一条记录的时间
 */
int log_client_query_stat(int fd, int *count, time_t *begin, time_t *end);

/*
 * @brief   通过网络写日志的具体实现
 */
int log_client_write_impl(int fd, enum log_level lv, const char * content);

/*
 * @brief   具有可变参数的写日志(写到网络上的日志服务端去)
 */
#define log_client_write(fd, level, format, args...) \
    do { \
        char log_buf[MAX_LOG_LEN];\
        memset(log_buf, 0, MAX_LOG_LEN);\
        snprintf(log_buf, MAX_LOG_LEN, format, ##args);\
        log_client_write_impl(fd, level, log_buf);\
    } while(0)

/*
 * @brief   以无连接的形式发送日志，不需要先创建客户端
 */
int log_client_write2_impl(const char *server, enum log_level lv,
        const char * content);

/*
 * @brief   具有可变参数的写日志， 无连接形式的版本
 */
#define log_client_write2(server, level, format, args...) \
    do { \
        char log_buf[MAX_LOG_LEN];\
        memset(log_buf, 0, MAX_LOG_LEN);\
        snprintf(log_buf, MAX_LOG_LEN, format, ##args);\
        log_client_write2_impl(server, level, log_buf);\
    } while(0)

/*
 * @brief   下载日志文件到本地fname指定的文件中
 *
 * @return  0,成功且写入完成
 *          -1: fd错误，且fd应当被关闭不再使用
 *          -2: fname相关错误，如文件打开失败等。
 */
int log_client_download(int fd, const char *fname);

#endif // LOG_CLIENT_CODE

/**************************************************
 * 五、日志服务器
 */
#ifdef LOG_SERVER_CODE

#define LOG_PORT    9528    // 控制日志服务器所监听的TCP端口

/*
 * @brief   启动服务端
 * @warning 多次调用只启动一个服务器实例
 */
void log_server_start();

#endif // LOG_SERVER_CODE

#ifdef __cplusplus
}
#endif

#endif /* LOG_CM_LOG_H_ */
